Automatyzacja analizy danych w R: Zastosowanie pipeline’ów, workflows i tidymodels

Automatyzacja procesów analizy danych jest kluczowa w wielu dziedzinach, od biznesu po nauki społeczne i medycynę. Z rosnącym wolumenem danych oraz złożonością analizowanych problemów, automatyzacja staje się coraz bardziej niezbędna. Pozwala ona efektywnie zarządzać danymi, przyspieszać cykl rozwoju modeli, eliminować błędy ludzkie oraz zwiększać skuteczność analizy.

Tidymodels

Jest to zestaw narzędzi do modelowania danych w R, które obejmują szereg pakietów, takich jak parsnip do definiowania modeli, recipes do przetwarzania wstępnego danych, workflows do organizowania procesów modelowania, tune do strojenia hiperparametrów i inne.

library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.1.1 ──
## ✔ broom        1.0.5      ✔ recipes      1.0.10
## ✔ dials        1.2.1      ✔ rsample      1.2.0 
## ✔ ggplot2      3.5.0      ✔ tibble       3.2.1 
## ✔ infer        1.0.6      ✔ tidyr        1.3.1 
## ✔ modeldata    1.3.0      ✔ tune         1.2.1 
## ✔ parsnip      1.2.0      ✔ workflows    1.1.4 
## ✔ purrr        1.0.2      ✔ workflowsets 1.0.1
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ purrr::discard() masks scales::discard()
## ✖ dplyr::filter()  masks stats::filter()
## ✖ dplyr::lag()     masks stats::lag()
## ✖ recipes::step()  masks stats::step()
## • Learn how to get started at https://www.tidymodels.org/start/

Workflows

Workflow to obiekt, który może łączyć ze sobą żądania dotyczące przetwarzania wstępnego, modelowania oraz przetwarzania końcowego. Na przykład, jeśli mamy receptę (recipe) i model parsnip, mogą one być połączone w workflow. Zalety to:

  1. Nie ma potrzeby śledzenia osobnych obiektów w swoim środowisku roboczym.

  2. Przygotowanie recepty i dopasowanie modelu można wykonać za pomocą pojedynczego wywołania funkcji fit().

library(recipes)
library(parsnip)
library(workflows)

Pipelines

Pipelines są prostym sposobem na zachowanie uporządkowanego kodu przetwarzania danych i modelowania. Konkretnie, pipeline łączy kroki przetwarzania danych i modelowania, dzięki czemu możemy używać całego zestawu jak jednego kroku.

%>% lub |>

Zalety:

  1. Czystszy kod: Dzięki nim nie trzeba ręcznie śledzić danych na każdym etapie przetwarzania.

  2. Mniej błędów: Mniejsze ryzyko pominięcia lub źle zastosowania kroków przetwarzania danych.

Przykład

Zbiór danych

Zbiór danych zawiera informacje dotyczące nieruchomości w Melbourne, Australii. Dane te zostały zebrane przez Tony’ego Pino z publicznie dostępnych wyników opublikowanych co tydzień na stronie Domain.com.au. Zbiór obejmuje adresy nieruchomości, rodzaj nieruchomości, dzielnicę, metodę sprzedaży, liczbę pokoi, cenę, agenta nieruchomości, datę sprzedaży oraz odległość od centrum miasta.

data <- read.csv("melb_data.csv")
head(data)
# Podział danych na zbiór treningowy i testowy
set.seed(0) 
train_index <- sample(1:nrow(data), 0.8 * nrow(data)) # Indeksy wierszy dla zbioru treningowego
X_train_full <- data[train_index, ]
X_valid_full <- data[-train_index, ]
y_train <- X_train_full$Price
y_valid <- X_valid_full$Price
# Wybór kolumn kategorycznych i numerycznych
categorical_cols <- names(Filter(is.character, X_train_full)) #kolumny kategoryczne z zbioru treningowego
numerical_cols <- names(Filter(is.numeric, X_train_full)) #kolumny numeryczne z zbioru treningowego 

# Wybór kolumn z niską kardynalnością (czyli stosunkowo niską liczbą unikalnych wartości <10 w tym przypadku)
selected_categorical_cols <- character()
for (col in categorical_cols) {
  if (length(unique(X_train_full[[col]])) < 10) {
    selected_categorical_cols <- c(selected_categorical_cols, col)
  }
}
# Stworzenie zbiorów treningowego i testowego zawierających wybrane kolumny
X_train <- X_train_full[c(selected_categorical_cols, numerical_cols)]
X_valid <- X_valid_full[c(selected_categorical_cols, numerical_cols)]
head(X_train)

Proces tworzenia automatyzacji modelowania danych

Najpierw definiujemy kroki przetwarzania danych, następnie określamy specyfikację modelu, łączymy je w workflow oraz dopasowujemy model i dokonujemy oceny jego działania. Całość odbywa się w sposób uporządkowany i spójny, co ułatwia zarządzanie i analizę danych w kontekście modelowania.

Krok 1: Definiowanie kroków przetwarzania

# Definicja preprocessingu
rec <- recipe(Price ~ ., data = X_train) %>%
  step_impute_median(all_numeric(), -all_outcomes()) %>%
  step_other(all_nominal(), -all_outcomes(), threshold = 0.05) %>%
  step_dummy(all_nominal(), -all_outcomes())

Krok 2: Definiowanie Modelu

# Definicja modelu
model_spec <- rand_forest(trees = 100) %>%
  set_mode("regression") %>%
  set_engine("randomForest")

Krok 3: Tworzenie Workflow

# Tworzenie potoku
wflow <- workflow() %>%
  add_recipe(rec) %>%
  add_model(model_spec)

# Dopasowanie modelu
fit <- fit(wflow, data = X_train)

Krok 4: Predykcja i Ocena Modelu

# Predykcje
preds <- predict(fit, new_data = X_valid)$.pred
## 
## Dołączanie pakietu: 'Metrics'
## Następujące obiekty zostały zakryte z 'package:yardstick':
## 
##     accuracy, mae, mape, mase, precision, recall, rmse, smape
MAE_score <- mae(y_valid, preds)
RMSE_score <- rmse(y_valid, preds)
MAPE_score <- mape(y_valid, preds)

print(paste('MAE:', MAE_score))
## [1] "MAE: 168048.412293939"
print(paste('RMSE:', RMSE_score))
## [1] "RMSE: 312653.715323329"
print(paste('MAPE:', MAPE_score))
## [1] "MAPE: 0.150332771446798"

Wykres residuów

# Tworzenie ramki danych z prawdziwymi i przewidywanymi wartościami
data_ <- data.frame(truth = y_valid, estimate = preds)
data_$residuals <- round(data_$truth - data_$estimate, 2)
library(plotly)
## 
## Dołączanie pakietu: 'plotly'
## Następujący obiekt został zakryty z 'package:ggplot2':
## 
##     last_plot
## Następujący obiekt został zakryty z 'package:stats':
## 
##     filter
## Następujący obiekt został zakryty z 'package:graphics':
## 
##     layout
# Tworzenie interaktywnego wykresu residuów za pomocą plotly
plot_ly(data_, x = ~estimate, y = ~residuals, type = "scatter", mode = "markers",
        marker = list(color = ~residuals, colorscale = "Viridis"),
        text = ~paste("Przewidywane wartości: ", round(estimate, 2), "<br>",
                      "Residua: ", residuals),
        hoverinfo = "text") %>%
  layout(title = "Wykres residuów",
         xaxis = list(title = "Przewidywane wartości"),
         yaxis = list(title = "Residua"))
sum(abs(data_$residuals) < 1000)
## [1] 18
data_ %>% filter(abs(data_$residuals) < 1000)